﻿using System;
using System.Runtime.CompilerServices;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Scene;

namespace RayDen.Library.Components.Surface
{


    [Flags]
    public enum BrdfType : byte
    {
        Unknown = 0,
        Diffuse = 0x80,
        Specular = 0x40,
        Glossy = 0x10,
        Lightsource = 0x08,
        Refractive = 0x04,
        Reflection = 0x20,
        WavelengthDependent = 0x02,
        All = Diffuse | Specular | Glossy | Refractive
    }
    public static class IBsdfExtensions
    {
        public static bool Has(this BrdfType val, BrdfType f)
        {
            //return val.HasFlag(f);
            return ((byte)val & (byte)f).Equals((byte)f);
        }
        public static bool Has(this BsdfEvent val, BsdfEvent f)
        {
            //return val.HasFlag(f);
            return ((byte)val & (byte)f).Equals((byte)f);
        }

        public static BsdfEvent TypeToEvent(this BrdfType type)
        {
            var result = BsdfEvent.None;

            if (type.Has(BrdfType.Refractive))
            {
                result |= BsdfEvent.Transmit;
            }
            else
            {
                result |= BsdfEvent.Reflect;
            }

            if (type.Has(BrdfType.Diffuse))
            {
                result |= BsdfEvent.Diffuse;
            }

            if (type.Has(BrdfType.Glossy))
            {
                result |= BsdfEvent.Glossy;
            }

            if (type.Has(BrdfType.Specular))
            {
                result |= BsdfEvent.Specular;
            }


            return result;
        }


    }
    [Flags]
    public enum BsdfEvent :byte
    {
        None = 0,
        Diffuse = 1,
        Glossy = 2,
        Specular = 4,
        Reflect = 8,
        Transmit = 16,
        Absorb = 32,
        Environment = 64,
        Light = 128,
        Volume = 192,
    }

    public enum BrdfClass : byte
    {
        DiffuseLambert,
        Mirror,
        MatteMirror,
        Glass,
        ArchGlass,
        Alloy,
        GlossyMetal,
        Distribution,
        Measured,
        OrenNayar,
        AnisotropicPhong,
        Experimental,
    }

    [Serializable]
    public abstract class BrdfBase
    {
        public MaterialInfo MaterialData;
        public virtual string BrdfName
        {
            get { return this.GetType().Name; }
        }

        public virtual string Name
        {
            get { return this.MaterialData.Name; }
        }

        public MaterialInfo MData
        {
            get { return this.MaterialData; }
        }

        public float ContinuationProb
        {
            get
            {
                if (this.MaterialData == null)
                {
                    return 0.5f;
                }
                return ((this.MaterialData.DiffuseReflectance + this.MaterialData.GlossReflectance + this.MaterialData.Ka) / 3f).Filter();
            }
        }

        protected BrdfBase()
        {
        }
        

        public abstract BrdfType Type { get; }
        public abstract bool IsLightSource();
        public abstract bool IsDiffuse();
        public abstract bool IsRefractive();
        public abstract bool IsSpecular();
        public static float Epsilon = 1e-6f;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float SphericalTheta(ref Vector v)
        {
            return (float)Math.Acos(v.z);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float SphericalPhi(ref Vector v)
        {
            return (float)Math.Atan2(v.y, v.x) + MathLab.M_PI;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float CosTheta(ref Vector w) { return w.z; }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float AbsCosTheta(ref Vector w) { return Math.Abs(w.z); }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float SinTheta2(ref Vector w)
        {
            return 1f - CosTheta(ref w) * CosTheta(ref w);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float CosPhi(ref Vector w)
        {
            return w.x / SinTheta(ref w);
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float SinPhi(ref Vector w)
        {
            return w.y / SinTheta(ref w);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float SinTheta(ref Vector w)
        {
            return MathLab.Sqrt(Math.Max(0f, 1f - w.z * w.z));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool SameHemisphere(ref Vector w, ref Vector wp)
        {
            return w.z * wp.z > 0f;
        }

        public static Point BRDFRemap(ref Vector wo, ref Vector wi)
        {
            float cosi = CosTheta(ref wi), coso = CosTheta(ref wo);
            float sini = SinTheta(ref wi), sino = SinTheta(ref wo);
            float phii = SphericalPhi(ref wi), phio = SphericalPhi(ref wo);
            float dphi = phii - phio;
            if (dphi < 0f) dphi += 2f * MathLab.M_PI;
            if (dphi > 2f * MathLab.M_PI) dphi -= 2f * MathLab.M_PI;
            if (dphi > MathLab.M_PI) dphi = 2f * MathLab.M_PI - dphi;
            return new Point(sini * sino, dphi / MathLab.M_PI, cosi * coso);
        }

    }
}
